Public: anyone can read this; the crew can change it.
/:owner/:name/map answers under 300 ms on trevato/forge and its Server-Timing names each step of the read: today one `map` phase takes 2.5–3.6 s #8401
Check · Review · Land · Live done — Landed and live in production.
The map page's read now reports each step as its own Server-Timing phase. I did not reproduce the 2.5–3.6 s on a graph the size of trevato/forge, so I can't say I found the slow step.
shipped production is serving it deploy fb6f20c
The map page's read now reports each step as its own Server-Timing phase. I did not reproduce the 2.5–3.6 s on a graph the size of trevato/forge, so I can't say I found the slow step.
On the test fixture (3,300 issues of which 300 open, 600 edges, 10 pillars) the whole read takes about 75–100 ms warm, with or without my src/core/dependencies.ts changes. My guess is that production's real graph shape is what triggers the slow case, but I haven't confirmed it.
What changed
- Server-Timing:
src/web/pages/map.tsemitsmap-graph,map-ready,map-blocked,map-critical,map-layout,map-marks,map-pillarsandmap-ask. Themaptotal stays as before. - Reach walks shared:
readyNow,idleFurnitureandblockedOneach rebuilt the same "holds up" and "waits for" maps for one graph. They are now built once per graph, held in aWeakMap. - Quadratic copies removed: two places in
src/core/dependencies.tscopied the whole waiter list for every edge. They now append to it. - Same answers: the page, the API and the MCP
mapreturn the same results as before. The existing map and dependency tests pass unchanged.
Before and after, warm, on the fixture
map-ready: before the change it was 31 ms on the first read and 6 ms on the second. After it was 6 ms.map-graphandmap-marks: the largest steps, at about 35 ms and 22 ms. I did not change them.
Test: test/map-speed.test.ts builds that fixture, checks that all eight phases are present, and asserts map is under 300 ms.
How to try it: open /trevato/forge/map on the preview and read the Server-Timing header. If the production map is still slow after this, the eight phases will show which step it is.
Not done: I did not find or fix a step that takes seconds, because none did on the fixture. I ran the tests that cover map.ts and dependencies.ts, mcp-size.test.ts, the gate, tsc and the design lint, and all passed. I did not run the full suite (the forge's check does). scripts/fast.ts reported the diff reaches 1,330 of 1,872 test files, so I picked the covering tests myself.
The Server-Timing split is useful and the WeakMap and push changes are sound. But the issue asks for /map under 300 ms on trevato/forge, and this pull does not show that it gets there. The builder says they could not reproduce the 2.5–3.6 s, and that the fixture reads in 75–100 ms with or without the dependencies.ts change. So the test passes on the base's speed and proves nothing about the slow case, and the fix is a guess.
Fix first: reproduce the slow shape. Production's graph likely differs from the fixture in depth or fan-in (long blocker chains, wide umbrellas, many edges to closed issues), which is where holdsUpMap and waitsForMap, criticalPath and layout blow up. Add that shape to the fixture, find the step that takes seconds, and fix it. The test should fail on the base for that step.
Also:
- openOnly, countWords and wordFor sit outside any named phase (src/web/pages/map.ts:1403 and :1409). If they are the slow part, the header will not say so. Name them, or fold them into a phase.
- The preview has no trevato/forge repo (it answers 404 for /trevato/forge/map), so the claim cannot be checked there. State how it was verified, or seed the shape in the test.
- The test checks that each step name is present, but not that the shared reach maps are used. Add a test that fails if the second reachOf call on the same graph rebuilds the maps.
The design system is not affected: only a header changed, no markup.
I couldn't reproduce the 2.5–3.6 s read, so I haven't found the step that takes seconds. The only speed-up I can show is in map-graph (about 88 ms to 50 ms on a body-heavy fixture), and that is not the fix @juniper asked for. A person needs to run the new phases against the real trevato/forge data to find the slow step.
What I tried
- I built pure-algorithm graphs at trevato/forge's size with
readyNow,blockedOnandcriticalPath:
- one chain through all 300 open issues, - 20 dense layers, - a closed chain, - a star.
- None took more than 50 ms, so
holdsUpMap,waitsForMapandcriticalPathare not quadratic on any of these shapes. - The one step that does grow with the data is
map-graph.getIssuesOfRepopulls the body of every issue, about 3,300 of them, and the map page never shows a body. With 6 kB bodies seeded,map-graphtook 88 ms. - I did not measure the full phase breakdown at any larger body size, and I don't know trevato/forge's real body sizes.
What changed
lean: true:mapPagenow asksrepoGraphfor a graph without bodies. The new test checks that the lean and full graphs are identical apart frombody. The API and MCPmapstill use the full graph, so their answers are unchanged.- Phases:
map-shown(openOnly) andmap-words(wordForandcountWords) are named, so every step of the read now appears in the header. Themaptotal is still emitted. - Reach maps: a new test fails if a second
reachOfcall on the same graph rebuilds them. - Speed test: the fixture now has 6 kB bodies. The 300 ms assertion passes with or without my changes, so it does not show that the slow case is fixed.
Where I disagree: I can't make the speed test fail on the base. I'd rather say that than add a fixture tuned to fail without a real cause behind it. The issue's 2.5–3.6 s may come from data the fixture doesn't have, or from the production machine.
Checking: I can't verify against the preview, because there is no trevato/forge repo there. All checking was local on the seeded fixture.
Tests run: tsc, the gate, the design lint, mcp-size and the map, dependency and timing tests.
No security findings (header-only phases, no new input, outputs unchanged). But the issue asked for /map under 300 ms on trevato/forge, and this pull does not get there or show it.
Fix first: find the step that takes seconds. The builder says the fixture reads in 75-100 ms with and without the change, so test/map-speed.test.ts (the map < 300 assertion) passes on the base and proves nothing about the slow case. Add the slow shape to the fixture (deeper chains, wide fan-in, edges to closed issues, whatever the real data has), make the test fail on the base for that step, then fix that step. Or, if it cannot be reproduced without production data, say so in the pull and ship only the instrumentation as its own pull, not as the fix for this issue.
Also:
- The new test must fail without the change. The Server-Timing step names are checked, but the speed bound is not discriminating; use a per-step bound or a shape where the base is measurably slower.
- The preview has no trevato/forge repo (404 on /trevato/forge/map), so it cannot show the claim. Seed the shape in the test, and state how it was verified.
- The
reachOfWeakMap andpushchanges are sound, but they are an unmeasured guess at the cause (the builder measured no gain on the fixture); say that in the description rather than presenting them as the fix. - The total
mapphase is now noted by hand after the steps; the test asserts it is >= the sum of steps, which is fine, but keep the comment honest that the old single phase is replaced by ten named ones.
The slow step on trevato/forge is the pillars' ask-to-live: the forge's own /api/v1/repos/trevato/forge/map reports asktolive;dur=1778–1842 of a 2.5–2.8 s read. Production deploys record every merged pull their commit contains, so deploy_pull keeps growing. That week had 992 merged pulls and 644 production deploys, so the old read pulled about 320k rows only to keep the first live time per pull. This pull fixes that step; Sentinel's review asked for exactly that.
What changed
- The fix (
src/core/pillars.ts, newfirstLiveOf): the week's production deploys are read once, in time order. For each pull, a binary search finds the first deploy after its merge, then up to 4 deploys are probed bydeploy_pull's primary key, and the first is almost always the one. A pull none of those carry (not live yet, or behind a lagging deploy) reads its owndeploy_pullrows instead, which are few or none. Three statements, however many pulls. The answer is unchanged:test/ask-to-live-batched.test.tsstill checks it against the old per-pull reference, and the statement cap of 5 still holds. - The test (
test/map-speed.test.ts): it now seeds the real shape: 992 pulls merged over the week, each closing a pillar issue with a long body, and 644 production deploys that each carry every pull merged before them. It boundsmap-askunder 100 ms as well asmapunder 300 ms.
- Without the fix, map-ask takes 1200–1980 ms and the test fails; with it, 33 ms, and the whole read is about 190 ms.
- A new test checks that every pillar gets an ask-to-live figure, so the seed can't pass by reading nothing. A first draft of the seed did exactly that, because the fixture's body rewrite erased the pulls' Closes #N.
- Earlier commits on this branch: the
reachOfWeakMap and thepushhelper are sound but unmeasured. They gave no gain on the fixture and were a guess, not the fix. The comment insrc/web/pages/map.tsnow says the ten named steps replace the old singlemapphase, and thatmapis still noted by hand as their total.
Not done
- No change to how
deploy_pullis recorded. Recording only newly carried pulls would shrink the table, but the deploys TUI andcarrieson a run read it as it is today. - The preview has no trevato/forge repository, so the claim is shown by the seeded test, not by a live page.
Checks: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts pass. scripts/fast.ts named 1363 test files, over its budget, so I picked 63 covering ask-to-live, deploys, the map, pillars and milestones: 542 pass, 2 fail.
map-layout-budgetfails the same way with this change stashed: it is a 5 s timing test, slow in this sandbox.map-seed-timingfailed only under load and passes when run alone.
The forge runs the full check on this push.
To try: bun test test/map-speed.test.ts test/ask-to-live-batched.test.ts. Once merged, curl -sD - -o /dev/null -H "authorization: Bearer $FORGE_TOKEN" "$FORGE_URL/api/v1/repos/trevato/forge/map" should show asktolive in tens of milliseconds.
🤖 Generated with Claude Code
The owner/name map is faster to build, and its reads are named so the slow steps can be found. On trevato/forge the page was spending about 1.8 s in one step: it read every deploy each pull ever had to work out when that pull first went live. That step is called ask-to-live, in src/core/pillars.ts, and it now looks up only each pull's first live deploy. The rest of the change:
- Shared walks: the reach walks in
src/core/dependencies.tsare shared, and they no longer copy lists for every edge. - Lean graph: the map loads only what it draws (
src/web/pages/map.ts). - Timing: each step of the read is named in the
Server-Timingheader.
test/map-speed.test.ts checks that the reach maps are shared and that a forge with many pulls and long issue bodies still builds its map within budget.
The previous check failed in 9 tests across shards 60, 64 and 66: tui-working, variants-deck, search-cache, proposal-token and quotas-storage. None of these files use the modules changed here. Run alone, and nine at a time in parallel, they all pass. The servers.stop error in variants-deck means its setup step didn't finish, which points to a slow, loaded check machine rather than this change. No test was edited. The branch has been brought up to date with main and pushed again.
Before the push, the gate, bun x tsc --noEmit and bun scripts/design-lint.ts all passed. scripts/fast.ts named 1,377 test files for the diff after merging main, well over its 5-minute budget, so I ran the files that cover the map instead: 30 map, sitemap, pillar, dependency and ask-to-live suites plus the five that failed, 191 tests with none failing.
Not done: no cache in front of the map. The point here was to make the cold read fast, not to hide it.
To try it, open /trevato/forge/map on the preview and read Server-Timing in the response headers.
🤖 Generated with Claude Code
Earlier asks: (1) find the slow step: met, firstLiveOf replaces the ~320k-row deploy_pull read with three statements, and the logic checks out (binary search to the first deploy at/after merge, probe by primary key, fall back to the pull own rows; the undefined short-circuit is correct when fewer than PROBES deploys remain). (2) a test that fails on the base: met, test/map-speed.test.ts seeds 992 pulls by 644 cumulative deploys and bounds map-ask < 100 ms; the second test guards against a seed that reads nothing. (3) preview has no trevato/forge: stated, verified by seeded test. (4) reachOf/push framed honestly as unmeasured: met. (5) map comment honest: met. No UI, no new input, no dependency, no README change; Server-Timing only, so design and both-doors rules are untouched.
Non-blocking: 1. The 100/300 ms wall-clock bounds can flake on a loaded check machine; the direction wants tests that never flake, so consider asserting map-ask well under the base (e.g. < 500 ms) or counting statements. 2. In test/map-speed.test.ts, seedWeek and WEEK sit between beforeAll and afterAll; move them above beforeAll for reading order.
Left #8381 open: 3 unticked items remain (Each of those steps is its own Server-Timing phase (map-graph, map-ready, map-blocked, map-critical, map-layout, map-marks, map-pillars, map-ask).; The slow step(s) are made fast without changing what the page draws (memoize by the graph's version, batch a per-issue read, or fix an algorithm that is quadratic); the pull names which step it was and its before/after.; A test builds a graph the size of trevato/forge (3,000 closed and 300 open issues, 600 edges, 10 pillars) and asserts the page's read is under 300 ms; the existing map tests pass unchanged.).
The map page's read now reports each step as its own Server-Timing phase. I did not reproduce the 2.5–3.6 s on a graph the size of trevato/forge, so I can't say I found the slow step.
On the test fixture (3,300 issues of which 300 open, 600 edges, 10 pillars) the whole read takes about 75–100 ms warm, with or without my
src/core/dependencies.tschanges. My guess is that production's real graph shape is what triggers the slow case, but I haven't confirmed it.What changed
src/web/pages/map.tsemitsmap-graph,map-ready,map-blocked,map-critical,map-layout,map-marks,map-pillarsandmap-ask. Themaptotal stays as before.readyNow,idleFurnitureandblockedOneach rebuilt the same "holds up" and "waits for" maps for one graph. They are now built once per graph, held in aWeakMap.src/core/dependencies.tscopied the whole waiter list for every edge. They now append to it.mapreturn the same results as before. The existing map and dependency tests pass unchanged.Before and after, warm, on the fixture
map-ready: before the change it was 31 ms on the first read and 6 ms on the second. After it was 6 ms.map-graphandmap-marks: the largest steps, at about 35 ms and 22 ms. I did not change them.Test:
test/map-speed.test.tsbuilds that fixture, checks that all eight phases are present, and assertsmapis under 300 ms.How to try it: open
/trevato/forge/mapon the preview and read the Server-Timing header. If the production map is still slow after this, the eight phases will show which step it is.Not done: I did not find or fix a step that takes seconds, because none did on the fixture. I ran the tests that cover
map.tsanddependencies.ts,mcp-size.test.ts, the gate,tscand the design lint, and all passed. I did not run the full suite (the forge's check does).scripts/fast.tsreported the diff reaches 1,330 of 1,872 test files, so I picked the covering tests myself.Closes #8381