Repository navigation
perf(router): remove __debug_manifest endpoint + dead trie ancestry (−21% manifest) - #728
Merged
Merged
Conversation
commit: |
…ancestry (21% smaller manifest) The `?__debug_manifest` query endpoint (rsc/handler.ts) was an early experiment — an opt-in JSON dump of the manifest/trie/ancestry for inspection, gated by the internal `allowDebugManifest` option. Removed end to end: the endpoint, the option, and `extractAncestryFromTrie`. Its only data dependency was the per-leaf ancestry array (`TrieLeaf.a` — mount shortCodes from root to route), which route matching never reads (verified: trie-matching.ts and find-match.ts touch it zero times). With the sole reader gone the field was pure dead weight, so the whole ancestry pipeline is deleted: `a` off the leaf, the `routeAncestry` argument threaded through buildRouteTrie/buildPerRouterTrie and the recursive manifest builder, `captureAncestry`, and the `_routeAncestry` internal manifest field. buildPerRouterTrie's old "no ancestry => no routes" guard becomes an explicit empty-routeManifest check. Effect: the per-router manifest chunk drops 5.71MB -> 4.47MB (-21.6%) at 26k routes (the ancestry arrays plus their double-quote escaping) on every runtime — it shrinks the JSON.parse floor everywhere, not just the compile. A side benefit: the per-router trie is now fully mount-index- independent (nothing embeds the mount index), a strictly stronger dev/prod parity guarantee. The shortCode SYSTEM (server/context.ts counter keys, the DSL) is untouched — only the trie-leaf ancestry snapshot is removed. The separate `debugManifest()` router method (router/debug-manifest.ts, re-walks handlers -> SerializedManifest) is a different surface and is left intact. Verified: typecheck; unit (1195+ across all packages, incl. trie/ find-match/per-router/dev-prod-parity — dev-prod-parity now asserts full byte-equality across mount indices); semantic-matrix 67/67; stress-demo build matches real routes (200) + genuine miss (404) with ancestry gone; lint; format.
ivogt
force-pushed
the
perf/drop-debug-manifest
branch
from
July 7, 2026 21:30
c89bb16 to
60d2277
Compare
5 tasks done
ivogt
added a commit
that referenced
this pull request
Jul 7, 2026
…ll and guide #728 deleted the ?__debug_manifest HTTP endpoint and its routeAncestry data, but the debug-manifest skill and docs guide still told users to open http://localhost:PORT/?__debug_manifest and documented the deleted { routerId, routeManifest, routeAncestry, routeTrie, precomputedEntries } shape. Remove both browser-endpoint sections; both files already carry the correct router.debugManifest() programmatic path ({ routes, layouts, totalRoutes, totalLayouts }), now the primary documented route.
ivogt
added a commit
that referenced
this pull request
Jul 8, 2026
…ites (#725 gap) + drop removed __debug_manifest docs (#730) * fix(router): handler-invoked loaders under cache() throw on header writes (closes #725 gap) The cached-scope header guard (#725) exempted every loader body under a cache() boundary via isInsideAnyLoaderScope(). That let a handler-invoked loader — await ctx.use(Loader) from a handler, never registered with loader() — write a cookie/header on the MISS. On a HIT the handler is skipped and only DSL-registered loaders re-run, so that loader never executes and its write silently vanishes — the exact MISS/HIT divergence #725 was built to eliminate. Narrow the exemption to isInsideLoaderScope() (DSL scope only): registered loaders re-run on every HIT and merge into every response, so their writes stay allowed; a handler-invoked loader now throws deterministically like a handler write. The DSL scope ALS survives nested ctx.use bodies, so a handler-invoked loader nested under a DSL loader stays exempt (its parent re-runs it every HIT). The broad predicate is deferred to the throw path for the error label so the PPR handler-invoked-loader message stays "loader", and the throw reason keys on scope.kind so the cache() case explains the handler-skip cause. Re-ground the neighbouring read-purity guard comment, which this change makes contradictory: reads stay on the broad predicate under the consumption-lane baked-copy tradeoff (#672/#674), only writes narrow to DSL scope. Unit (cached-header-guard.test.ts): DSL-allowed pin, handler-invoked-throws pin (red before this change, green after), nested-under-DSL-allowed pin. E2e (cache-scope-guard, dev + production): /handler-loader-cookie-blocked asserts the error boundary renders and the cookie never reaches the wire; the DSL /loader-cookie-allowed control stays green in both modes. * docs: drop the removed __debug_manifest browser endpoint from the skill and guide #728 deleted the ?__debug_manifest HTTP endpoint and its routeAncestry data, but the debug-manifest skill and docs guide still told users to open http://localhost:PORT/?__debug_manifest and documented the deleted { routerId, routeManifest, routeAncestry, routeTrie, precomputedEntries } shape. Remove both browser-endpoint sections; both files already carry the correct router.debugManifest() programmatic path ({ routes, layouts, totalRoutes, totalLayouts }), now the primary documented route.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Removes the
?__debug_manifestquery endpoint (an early opt-in JSON dump of the manifest/trie for inspection) and, with it, the dead per-leaf trie ancestry it was the only consumer of. Net effect: the per-router manifest chunk is 21.6% smaller (5.71MB → 4.47MB at 26k routes) on every runtime, with no behavior change.Why it's safe (nothing user-facing breaks)
The endpoint's only data dependency was
TrieLeaf.a— the ancestry shortCodes on every trie leaf. Route matching never reads it (verified:trie-matching.tsandfind-match.tsreferenceleaf.azero times; the source already flagged it debug-only). So once the endpoint andextractAncestryFromTrieare gone,ais pure dead weight, and removing it can't change matching,href()/reverse(), or any request behavior.Deliberately not touched:
shortCodesystem (mount indices used inserver/context.tscounter keys and the DSL) — that's load-bearing; only the trie-leaf snapshot of it is removed.debugManifest()router method (router/debug-manifest.ts) — a different surface that re-walks handlers into aSerializedManifest; left fully intact.Changes
rsc/handler.ts— delete the__debug_manifestendpoint block.router.ts/router-options.ts/router-interfaces.ts— remove the internalallowDebugManifestoption;cache/cache-key-utils.ts— drop it from the reserved-param set.build/route-trie.ts— removeTrieLeaf.a, therouteAncestryparam frombuildRouteTrie, andextractAncestryFromTrie;buildPerRouterTrie's "no ancestry ⇒ no routes" guard becomes an explicit empty-routeManifestcheck.build/generate-manifest.ts— removecaptureAncestry, the_routeAncestryfield, and therouteAncestrythreading through the recursive builder.vite/discovery/discover-routers.ts— drop the_routeAncestrymerge and the now-redundant guard.a:leaves / passed the ancestry arg are updated (5 mechanical, parallelized);per-router-manifest.test.tsswaps itsextractAncestryFromTrie-based route-name assertions for a localtrieRouteNames()walk;dev-prod-trie-parity.test.tsnow asserts the stronger invariant — the per-router trie is byte-identical across mount indices (nothing embeds the mount index anymore).feature-map.md(dropallowDebugManifest),matching-and-lazy-discovery.md(remove theleaf.acaveat + the "stop serializingleaf.a" backlog item this completes).Effect
JSON.parsefloor, not just compile. Negligible for normal apps (the win scales with route count), meaningful at the tail.Test plan
pnpm run typecheckpnpm run test:unit:all— 1195+ tests across all packages, incl.find-match,trie-matching,trie-regex-parity,per-router-manifest,dev-prod-trie-parity.semantic-matrix67/67 (dev + production) — middleware scope/ordering/context visibility/PE-JS parity/PPR all intact.M0L0R/"a":[markers gone, real routes 200, genuine miss 404.pnpm run lint,pnpm run format,pnpm check:e2e-bucketing.Notes
allowDebugManifestwas@internal(not documented public API), so removal follows the pre-release "remove, don't deprecate" hygiene rule.