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

Real-user page timings roll up per page on the ops view, one line per page over budget #10580

Merged opened this

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

Merged

Page timings that browsers report now roll up per page, and `/system` and the terminal show one line for each page over budget. `bun x tsc --noEmit`, `bun scripts/gate.ts` and `bun scripts/design-lint.ts` came back clean. The new test file passed, along with `test/loop-stall-by-route.test.ts` and `test/system-parts.test.ts`. I ran no other tests; the forge runs the full check on this push. I never…

shipped production is serving it deploy da127e7

opened this

Page timings that browsers report now roll up per page, and /system and the terminal show one line for each page over budget. bun x tsc --noEmit, bun scripts/gate.ts and bun scripts/design-lint.ts came back clean. The new test file passed, along with test/loop-stall-by-route.test.ts and test/system-parts.test.ts. I ran no other tests; the forge runs the full check on this push. I never ran the app, so no real /system page has rendered the new section.

What changed

  • Roll-up: slowPages in src/perf/watch.ts reads page_vital for the last hour and works out p95 time to first byte ("server") and p95 largest paint ("first paint") for each page pattern. It uses the budget I added in src/perf/budgets.ts: server 150 ms, first paint 1 s. A page needs at least 10 reports before it is judged.
  • Slowest phase: server when time to first byte is most of the wait. Otherwise render, meaning the time from first byte to first paint.
  • Ops view: /system gets a "Pages over budget, last hour" section, one line per page, worst first. When every page is fast, nothing renders: no heading and no empty message.
  • Terminal: src/tui/screens/slowpages.ts adds a "Pages over budget" palette entry for admin people, one line per page with the same words as /system. The entry is only offered when a page is over budget. The screen itself is not covered by the test, only its line function.
  • Tests: test/slow-pages.test.ts covers the quiet case and the one-line-per-page case. It also covers the minimum-reports and last-hour cutoffs, who can see the terminal lines, and escaping of a route name.
  • Docs and push: docs/features/slow-pages.md. The pre-push hook demanded a "Try:" commit, so the second commit is an empty one that only carries that line.

Left out

  • No finding or issue is filed from real-user timings. This is a view only; the existing watcher still works from server-side samples.
  • "Server" is the browser's time to first byte, so it includes network time. The beacon sends nothing finer.
  • "First paint" is the largest contentful paint, the only paint the beacon reports.
  • The window (1 hour) and the 10-report minimum are constants in src/perf/watch.ts, not settings.

Try it: as the owner, open /system. The section appears once a route has 10 or more reports in the last hour with p95 server time over 150 ms or p95 first paint over 1 s. In the terminal, the "Pages over budget" entry appears in the palette in the same case.

🤖 Generated with Claude Code

Closes #10536

commented

Page timings that browsers report now roll up per page, and /system and the terminal show one line for each page over budget. bun x tsc --noEmit, bun scripts/gate.ts and bun scripts/design-lint.ts came back clean. The new test file passed, along with test/loop-stall-by-route.test.ts and test/system-parts.test.ts. I ran no other tests; the forge runs the full check on this push. I never ran the app, so no real /system page has rendered the new section.

What changed

  • Roll-up: slowPages in src/perf/watch.ts reads page_vital for the last hour and works out p95 time to first byte ("server") and p95 largest paint ("first paint") for each page pattern. It uses the budget I added in src/perf/budgets.ts: server 150 ms, first paint 1 s. A page needs at least 10 reports before it is judged.
  • Slowest phase: server when time to first byte is most of the wait. Otherwise render, meaning the time from first byte to first paint.
  • Ops view: /system gets a "Pages over budget, last hour" section, one line per page, worst first. When every page is fast, nothing renders: no heading and no empty message.
  • Terminal: src/tui/screens/slowpages.ts adds a "Pages over budget" palette entry for admin people, one line per page with the same words as /system. The entry is only offered when a page is over budget. The screen itself is not covered by the test, only its line function.
  • Tests: test/slow-pages.test.ts covers the quiet case and the one-line-per-page case. It also covers the minimum-reports and last-hour cutoffs, who can see the terminal lines, and escaping of a route name.
  • Docs and push: docs/features/slow-pages.md. The pre-push hook demanded a "Try:" commit, so the second commit is an empty one that only carries that line.

Left out

  • No finding or issue is filed from real-user timings. This is a view only; the existing watcher still works from server-side samples.
  • "Server" is the browser's time to first byte, so it includes network time. The beacon sends nothing finer.
  • "First paint" is the largest contentful paint, the only paint the beacon reports.
  • The window (1 hour) and the 10-report minimum are constants in src/perf/watch.ts, not settings.

Try it: as the owner, open /system. The section appears once a route has 10 or more reports in the last hour with p95 server time over 150 ms or p95 first paint over 1 s. In the terminal, the "Pages over budget" entry appears in the palette in the same case.

🤖 Generated with Claude Code

Changes requested — before the latest push

The roll-up, gating and escaping are sound, and docs/features/slow-pages.md is in the right place (README untouched). Three changes before it lands:

  1. FIRST: the terminal screen and palette entry have no test. slowPagesScreen, entry.offered (the "no palette entry when every page is fast" behaviour the doc promises) and the run push are uncovered; the scout said so. Add a test that entry.offered is false for a fast forge and for a non-admin, true for an admin with a slow page, and that rendering the screen shows the lines.
  2. Layering: src/perf/watch.ts now imports dayOf from src/web/measure.ts, so the perf layer depends on the web layer. Move the day-key helper (or the roll-up query) to a module both can import, or keep the query beside recordVital.
  3. /system renders slowPages on the request path and pulls every page_vital row of the last hour into JS to compute p95. On a busy forge that is unbounded work against the 50 ms budget. Aggregate or cap in SQL (per-route LIMIT or a pre-grouped count first), or cache for a few seconds like the other sections.

Smaller: the section sits in #sys-slow-pages but I see no live update wiring like the other sys-* blocks, so it only changes on reload; say so or wire it. Principle 8 (plain words): the page shows "server", "render" and "slowest phase" with no explanation; say "slowest part: the server / the browser drawing it". No design-system departures otherwise: it uses settings-block and list, no new accent, no JS needed.

commented

Real-user page timings now roll up per page, and /system and the terminal each show one line for every page over budget. A fast forge shows nothing. All three of juniper's requested changes and the smaller notes are done and pushed.

I ran bun scripts/gate.ts, tsc --noEmit and design-lint; all pass. The new and changed slow-pages tests pass, and so do the existing system tests. I did not run the full suite. I did not check the preview after the push.

Changes

  1. Terminal tests. New test/slow-pages-tui.test.ts covers three things:

- The palette entry is not offered on a fast forge, and not offered to a non-admin or an anonymous session. - It is offered to an admin once a page is slow. - entry.run pushes slowPagesScreen, and the screen draws the slow page's line and not the fast one.

  1. Layering. dayOf now lives in src/perf/day.ts. src/perf/watch.ts imports it from there, and src/web/measure.ts re-exports it, so existing callers are unchanged. src/perf/views_store.ts still imports dayOf from web/measure.ts; I left it alone.
  2. Request-path cost. slowPages no longer reads the whole hour into JS:

- SQL counts reports per route and keeps only routes with at least 10, busiest 50 first. - It then reads the newest 500 reports of each route to compute p95. - /system also keeps the result for 5 seconds, per database.

  1. Live updates. I did not wire this section into the live blocks. The doc now says it changes on reload.
  2. Plain words. The line now ends "slowest part: the server" or "slowest part: the browser drawing it", on the page and in the terminal. The doc uses the same words.

Left out

  • The group-by still scans the hour's page_vital rows by day, because no index covers route. It is bounded by the daily report cap, and I added no migration for it.
  • No test covers the 50-route or 500-report caps.

Try it Open /system as the owner after real visitors have reported slow pages. Or open "Pages over budget" in the terminal palette.

Approved

All earlier asks are met. The terminal screen and entry now have tests (admin only, offered only when a page is slow, the screen draws the line). dayOf moved to src/perf/day.ts. slowPages is bounded: SQL counts per route, at most 50 routes and 500 reports each, and /system keeps the result 5 s. The doc says the section changes on reload. The line ends in plain words. The how-to is in docs/features/slow-pages.md, not README. The section renders nothing when every page is fast, uses the existing settings-block and list components with no new accent, and works with JavaScript off. I read the diff and did not exercise the preview.

Two things to do next:

  1. Non-blocking: the terminal screen calls slowPages on every render and keypress with no cache. Give it the same short keep that /system has, or compute once when the screen opens.
  2. Non-blocking: add an index on page_vital(day, route, at) so the group-by stops scanning the whole day. Also drop the redundant e.n < PAGE_MIN_REPORTS check in slowPages, since SQL HAVING already enforces it.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs