diff --git a/AGENTS.md b/AGENTS.md index 320eda4098b..8d2d86f900c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,26 +2,29 @@ ## Scope +- Read [CONTRIBUTING.md](CONTRIBUTING.md) before analyzing or changing this repository; its contribution and PR requirements apply. - Read applicable nested `AGENTS.md` files before editing or testing a subtree. - Shared routing belongs in `packages/router-core`; check React, Solid, and Vue bindings for shared changes. Shared Start runtime/build logic lives in `packages/start-*-core`. +- Keep scratch checkouts outside the workspace's project-discovery tree to avoid duplicate Nx projects. Use separate benchmark results and build-output directories for independent comparisons. -## Work and validation +## Build and test constraints - Start bug fixes with a failing regression test that uses only public APIs to reproduce and assert user- or developer-visible behavior. Do not manufacture bugs by mutating internals. Establish whether real usage can reach the failing state before adding runtime handling; simplify handling of states proven unreachable. - When removing an API, option, or behavior, delete its tests. Never add tests that assert the removed thing is gone, such as `not.toHaveProperty('oldName')`, a type error for the old call, or a runtime throw. +- Diagnostic counters and profiles may observe a supported public path without changing its behavior. A diagnostic control proves only the measured mechanism; verify the final implementation through public behavior or the supported compiler-plugin boundary. +- For asynchronous setup and cleanup, identify the authoritative state and lifetime before adding flags, timers, or completion callbacks. Preserve intentional ownership transfers and remove obsolete paths when replacing an implementation. - For suspected dependency issues, verify the upstream root cause, then propose an upstream fix to the user, including for other `@tanstack/*` packages. **Never implement a workaround in this repository unless the user explicitly authorizes it.** -- During debugging/prototyping, keep fixes aligned with the PR's intended architecture. If making a test pass requires another flag, counter, copied deadline, or duplicate completion authority, stop and consolidate state and ownership. Remove superseded paths instead of accumulating patches and bundle growth. - Use Node from `.nvmrc` and pnpm from root `package.json`. Install at the root with `CI=1 pnpm install --frozen-lockfile`. -- For sandbox-blocked registry/store access, escalate the same command; report the blocker if unavailable. Do not change stores, delete dependencies/lockfiles, use `--force`/`--ignore-scripts`, or weaken workspace trust/build policies to bypass it. +- Diagnose registry, store, and network failures separately from dependency incompatibilities. Preserve the lockfile and workspace trust/build policies when troubleshooting installation; bypassing scripts or policies changes the environment being tested. - Never manually edit `pnpm-lock.yaml` or any `routeTree.gen.ts`. Regenerate the lockfile with `pnpm install --no-frozen-lockfile` after intentional dependency changes; regenerate route trees through app builds/dev servers or the generator fixture harness. -- Always use control-flow braces. Run `pnpm format` before validation. +- Always use control-flow braces. Format changed files with the repository's Prettier configuration; root `pnpm format` writes across the checkout. - Use Nx: workspace imports consume built packages, so direct runners can test stale dependencies. Inspect resolved targets; some are inferred. ```sh CI=1 NX_DAEMON=false pnpm nx run : --outputStyle=stream --skipRemoteCache ``` -- Run one Nx command at a time. For a ~20-second startup/graph stall, stop, run `pnpm nx reset`, and retry once, then escalate. Do not apply that timeout to running tests. +- Avoid overlapping Nx invocations that write to the same build outputs. If project-graph startup stalls, inspect logs and running processes before using `pnpm nx reset`; quiet output from running tests or benchmarks is not evidence of a graph stall. - For code changes, run affected packages' `test:unit`, `test:types`, and `test:eslint` where available, including affected consumers; use `test:e2e` for browser/app behavior and `test:build` for exports/build changes. Root `pnpm test` includes the full e2e suite. Published-code changes require `pnpm changeset`. ## Runtime rules @@ -35,5 +38,5 @@ CI=1 NX_DAEMON=false pnpm nx run : --outputStyle=stream --skipR ## Performance and final bundle pass -- For runtime/memory changes, compare identical baseline/candidate workloads using the relevant [benchmark guide](benchmarks/AGENTS.md): client navigation, Start SSR, or memory. Add coverage for unmeasured mechanisms. -- **Last phase for every change affecting emitted client JavaScript**, including core/build transforms: complete the full [bundle-size-optimization skill](.agents/skills/bundle-size-optimization/SKILL.md) after correctness and performance validation. The full workflow is mandatory. +- For performance audits, runtime lifecycles, hydration/RSC, HMR, dependency upgrades, chunk caching, build, or type-inference cost, use the [performance-review skill](skills/performance-review/SKILL.md). It selects the relevant measurements and regression checks. +- **Last phase for every change affecting emitted client JavaScript**, including core/build transforms: complete the full [bundle-size-optimization skill](skills/bundle-size-optimization/SKILL.md) after correctness and performance validation. Apply the workflow to the affected code and its consumers. diff --git a/e2e/AGENTS.md b/e2e/AGENTS.md index f373ecba4f3..a92cc8d45ba 100644 --- a/e2e/AGENTS.md +++ b/e2e/AGENTS.md @@ -6,4 +6,5 @@ - Use the same `E2E_PORT_KEY`-aware key throughout config, app, and dummy-server setup/teardown. Clean only that key's port files, before allocation, when `TEST_WORKER_INDEX === undefined`; worker config reloads must not remap a running server's port. - For new browser tests, reuse the local error-checking fixture. The shared `test` from `@tanstack/router-e2e-utils` fails on unexpected `console.error`, including hydration errors; allow only expected errors with `whitelistErrors`. - HMR/watch tests edit real app sources. Reuse existing edit/restore helpers such as `createHmrFileEditor`, restore edits in teardown even after failures, and preserve the suites' single-worker and Nx `parallelism: false` settings. +- For HMR, hydration/RSC assets, or deployment-cache tests, use the [performance-review skill](../skills/performance-review/SKILL.md) to select the applicable checks. They define repeated-edit, cancellation, warm-server, stylesheet-readiness, and old-tab cases that a successful initial render misses. - `@tanstack/router-e2e-utils:test:unit` is a placeholder. Validate helper changes through consuming e2e projects. diff --git a/packages/AGENTS.md b/packages/AGENTS.md index 0c63c3a15c6..e4e21a1e7b3 100644 --- a/packages/AGENTS.md +++ b/packages/AGENTS.md @@ -10,6 +10,7 @@ ## Tests - Inspect test commands: several `test:unit` scripts immediately exit or only echo a message. For these packages, validate behavior in the shared core or consuming e2e app; a successful placeholder target provides no unit coverage. +- Check test discovery and TypeScript/ESLint includes for newly added files. A passing source-only compiler matrix does not typecheck tests outside its includes; use the applicable test configuration and inspect executed cases. Keep existing compiler/lint rules intact when extending coverage. - For `*-router-ssr-query` changes, run `router-ssr-query-core` unit tests and the affected framework's `e2e/-start/query-integration` tests. The framework wrapper unit targets currently exit without running tests. - For single-command Vitest targets, append filters after `--`, e.g. `-- tests/link.test.tsx -t "preloading"`. - `solid-router` and `router-generator` chain two Vitest commands in `test:unit`. Trailing file/name/update arguments reach only the final command. Run the full target for verification: Solid needs browser and server suites; the generator needs fixture generation followed by typechecking. diff --git a/packages/router-plugin/AGENTS.md b/packages/router-plugin/AGENTS.md index e954110819e..52d289fa3f7 100644 --- a/packages/router-plugin/AGENTS.md +++ b/packages/router-plugin/AGENTS.md @@ -3,5 +3,6 @@ - Preserve one initialization of shared module state across reference and split chunks. Shared extraction includes transitive dependencies and whole destructured declarations; never extract `Route` or bindings that depend on it. The invariant suite in `tests/code-splitter.test.ts` covers these contracts. - `handleRouteUpdate` is stringified and injected into app modules. Keep runtime dependencies inside the function or explicitly emit them; extracting a helper to plugin module scope breaks the generated function. `tests/handle-route-update.test.ts` executes that emitted code. - Route HMR rebuilds indexes, clears `resolvePathCache`, replaces lazy-chunk ownership, and refreshes active routes while preserving Fast Refresh component identities. Keep these steps coordinated when adding derived state. +- For HMR adapter changes, execute generated statements for first import and later hot evaluation; first import must not modify a different router's same-ID route. Match the adapter's actual accept/dispose data lifecycle, including Webpack's fresh disposal data. Follow the [HMR/watch checks](../../skills/performance-review/references/checks.md#hmr-and-watch-lifetimes) for browser state, pending work, route deletion, and configuration replacement. - For code-splitting changes, add inputs under `tests/code-splitter/test-files//`. Regenerate and review reference, virtual, and shared snapshots for every grouping in [`tests/code-splitter.test.ts`](tests/code-splitter.test.ts). - Check [`tests/constants.ts`](tests/constants.ts) for framework coverage: the snapshot matrix currently covers React and Solid only. Validate affected Vue behavior in Vue router e2e apps. diff --git a/skills/bundle-size-optimization/SKILL.md b/skills/bundle-size-optimization/SKILL.md index 1b6b5ebe3af..137f0f3883f 100644 --- a/skills/bundle-size-optimization/SKILL.md +++ b/skills/bundle-size-optimization/SKILL.md @@ -1,6 +1,6 @@ --- name: bundle-size-optimization -description: Use when working in this repository on JS bundle size, gzip regressions, benchmark scenarios, source attribution, treeshaking, dead code elimination, or Rolldown annotations. +description: Use when working in this repository on JS bundle size, gzip regressions, benchmark scenarios, source attribution, treeshaking, dead code elimination, or bundler annotations. --- # Bundle Size Optimization @@ -10,6 +10,10 @@ description: Use when working in this repository on JS bundle size, gzip regress Optimize measured client bundles, not source text. The source of truth is `@benchmarks/bundle-size:measure`, `benchmarks/bundle-size/results/current.json`, and emitted JS in `benchmarks/bundle-size/dist/`. Use `benchmark:bundle-size:run` for local iterations. It uses the same measurement script and builds the selected packages through Nx. +Apply the measurement, attribution, and regression workflow to the affected code and consumers. Compare baseline and candidate in isolated builds with identical workloads and separate outputs; a separate worktree is one way to do this. + +For audits, comparable measurements, build-time cost, and adapter-specific verification, use the [performance-review skill](../performance-review/SKILL.md) for the investigation and its applicable reference checks. Reuse completed checks for the same implementation and workload when continuing into bundle measurement. The official runner does not include Webpack: changes affecting that adapter or shared splitting/DCE also require its real-consumer checks. Do not substitute a Vite/Rsbuild result for Webpack evidence. + ## Commands | Need | Command | @@ -34,12 +38,11 @@ For named results, pass `--current benchmarks/bundle-size/results/runs//cu ## Rules -- Run one Nx command at a time. +- Avoid concurrent measurements or builds that share output directories; use Nx's task graph to coordinate package dependencies. - Use the runner instead of shell chains for environment variables, redirection, exit codes, log tails, and result queries. -- For agent runs, use `pnpm --silent benchmark:bundle-size:run ...` to omit the package-manager command echo. - The runner sets `CI=1` and `NX_DAEMON=false`. It saves full output in `tests.log` and `measure.log` beside `current.json`. - If a step fails, the runner stops and prints at most 40 log lines, limited to 8,000 characters. Read the reported log for more detail. -- Quiet stdout does not indicate an Nx hang. Inspect the reported log for progress before applying the Nx reset/retry guardrail. +- Quiet stdout does not indicate an Nx hang. Inspect the reported log for progress before resetting Nx. - Use `--test-projects` for the packages that changed. Pass unit-test file selectors and flags after `--`. - The runner does not select tests automatically. Keep type tests, performance benchmarks, and e2e tests separate. - Keep baseline and candidate scenario selections and measurement flags identical. Use a new `--name` for each retained snapshot. @@ -47,18 +50,14 @@ For named results, pass `--current benchmarks/bundle-size/results/runs//cu - Track `gzipBytes` first; also inspect `initialGzipBytes`, `rawBytes`, `brotliBytes`, `jsFiles`, and per-file `files`. - Dist paths use `scenarioDir`/`outDir`, not metric ids: `react-router.minimal` maps to `dist/react-router-minimal/`. - For tiny changes, measure after each candidate; gzip can move opposite raw bytes. -- To compare a base commit, measure the same scenario in a separate worktree. Pass its saved `current.json` path to `--baseline`. +- To compare a base commit, measure the same scenario in an isolated build, such as a separate worktree. Pass its saved `current.json` path to `--baseline`. - Use history for prior patterns and baselines, not source attribution. It is commit-level data. - Runtime performance and security may never be sacrificed for bundle size. -- Do not stop after the first verified win. Keep iterating through reasonable local, emitted-JS, and algorithmic candidates until measured regressions, readability, or risk rule out the remaining paths. - When inlining helpers or simplifying non-obvious logic, preserve readability with a short comment explaining the meaning/invariant, not the mechanics. - Before inlining or deleting a helper/function, use the TypeScript language-service script to check references: `pnpm ts:symbol-references -- --project /tsconfig.json --file --symbol `. If the helper is used elsewhere, inlining one use is usually not worth it for bundle size unless measurement proves otherwise. If no references remain, delete the helper and verify with the script. - Run unit/types tests for the package being modified plus relevant e2e tests under `e2e/`. -- Continue optimizing until further reductions would make code unreadable/unmaintainable, or no more reductions remain. A user-provided byte target is not required. -- Be willing to make large, risky architectural or algorithmic changes, but only within the runtime, security, readability, maintainability, and test constraints above. -- If you are unsure whether to land a passing change because runtime semantics might change, add unit/e2e tests and/or ask clarifying questions. If codebase exploration can answer the question, explore instead. For each question, provide your recommended answer. +- Evaluate candidates against correctness, measured benefit, readability, and maintenance cost. Resolve uncertain runtime behavior through source review and focused tests before retaining a change. - If runtime performance implications are unclear, add a focused Vitest benchmark (`*.bench.ts`) comparing candidate implementations across realistic and edge-case inputs, like `packages/router-core/tests/closing-tag-detection.bench.ts`; verify implementations produce identical results before `bench()` cases. -- If you learn a reusable bundle-size pattern, hit a tooling gap, or lack analysis capability, ask the user before updating this skill or the benchmark scripts. ## Benchmark Rules @@ -67,8 +66,10 @@ For named results, pass `--current benchmarks/bundle-size/results/runs//cu - If a change can affect several package families, pick the smallest scenario that imports the shared code for quick iteration, then spot-check the next most likely affected family before finalizing. - Before finalizing, run the full bundle-size benchmark without `--scenario` and compare all scenarios. Look for outliers/anomalies even when the targeted scenario improved. - Benchmark the changed mechanism directly, not just the public API around it. +- Confirm the affected code survives into the measured scenario. If stock scenarios omit an optional feature such as deferred hydration or RSC, add a focused supported consumer and measure its cost separately; unchanged stock totals do not establish zero cost for that feature. +- For runtime/vendor chunk layout changes, follow the [chunk caching and deployment checks](../performance-review/references/checks.md#chunk-caching-and-deployment-recovery). Measure cold-load cost separately from repeat-deployment savings; a favorable transfer result does not establish faster loading. - Keep broad realistic scenarios as smoke/regression coverage; use focused cases for proof. -- Compare baseline and current with the same benchmark file. Use a separate worktree when only implementation should differ. +- Compare baseline and current with the same benchmark file; isolate the builds so only the implementation differs. - Run noisy benchmark families separately with `-t `; all-in-one suites can perturb tiny operations. - For branchy fast paths, include best-case, worst-case, and expected mixed distributions. - Batch ultra-fast operations inside one benchmark iteration when single calls are dominated by timer/outlier noise. @@ -84,7 +85,7 @@ Before calling an optimization final, prove which exact production hunks should 1. Snapshot the unoptimized baseline and the full candidate metrics. 2. Split the production diff into logical hunks or dependent hunk groups. Include syntax-only and readability-only edits if they can affect emitted code. 3. Benchmark each independent hunk alone against the same baseline. Benchmark relevant combinations when hunks only matter together or interact. -4. For each hunk/group, record bundle metrics and focused performance results when runtime cost could change. +4. For each hunk/group, measure bundle metrics and runtime performance where affected. 5. Keep only changes that improve bundle size or performance, or are required for correctness/tests/style and do not regress measured results. Revert neutral or harmful optimization-only changes. 6. Rebuild and remeasure the final composed version. It must not be larger or slower than the pre-attribution candidate unless the retained change is explicitly required for correctness or style. @@ -115,16 +116,14 @@ After each candidate, run focused perf benchmarks before bundle measurement. Rej When done optimizing: -1. Spawn 5 subagents to review the optimization diff against existing tests. Ask each to identify missing unit test cases that could fail with the current changes or newly uncovered edge cases, and missing performance benchmarks that could hide regressions. -2. If a possible regression is unclear, ask the user or explore the codebase until the expected behavior is clear. -3. Use their input to add focused unit tests and benchmarks. -4. Commit only the tests/benchmarks/supporting test-script changes. -5. Stash the implementation changes. -6. Run tests, performance benchmarks, and the relevant bundle-size measurement, then write BEFORE results to `RESULT-optimization-{topic}.md`. -7. Pop the implementation changes. -8. Run the same tests, performance benchmarks, and bundle-size measurement, then append AFTER results to the same file. -9. When reviewing benchmark output, consider statistical quality: standard deviation, margin of error, variance/noise, sample count, and percentiles. Re-run or narrow conclusions when results are noisy. -10. Compare BEFORE and AFTER. If anything regressed, iterate until green or revert the regression. +1. Review the optimization diff against existing tests for missing behavior cases, newly uncovered edge cases, and performance coverage. Verify review findings against source and executable checks. +2. Resolve uncertain behavior from the relevant APIs, implementation, and tests before accepting the optimization. +3. Use the review findings to add focused unit tests and benchmarks. +4. Prepare isolated baseline and candidate worktrees/snapshots with the same tests and benchmark harness, differing only in the implementation under comparison. Build each snapshot's dependencies through Nx; use separate results/dist paths and preserve the working checkout. +5. Run tests, performance benchmarks, and the relevant bundle-size measurement on the baseline. +6. Run the same checks on the candidate and compare against the baseline. Confirm that skipped or cached checks have not omitted the changed behavior. +7. When reviewing benchmark output, consider statistical quality: standard deviation, margin of error, variance/noise, sample count, and percentiles. Re-run or narrow conclusions when results are noisy. +8. Compare baseline and candidate results. Investigate regressions and recheck the final composed implementation after changes. Useful patterns: remove prod-only strings, remove unused exports, flatten wrappers, inline one-use helpers, avoid duplicate literals, improve treeshaking boundaries, simplify branches after preserving behavior. @@ -132,6 +131,8 @@ Useful patterns: remove prod-only strings, remove unused exports, flatten wrappe Rolldown removes code only when unused and side-effect-free. Property reads may trigger getters; storage/global access can observe or throw. +Validate annotation support and emitted effects with the installed bundler/minifier. For Webpack, use the [shipped-graph checks](../performance-review/references/checks.md#build-tool-checks), including unused-import removal, required CSS/global effects, and one-time initialization across eager/lazy chunks. + | Annotation | Valid | Unsafe | | ----------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | `/* @__PURE__ */ call()` | immediately before a call/new expression whose unused result can be dropped | declarations, property reads, setup, storage, DOM/history/listener code | diff --git a/skills/performance-review/SKILL.md b/skills/performance-review/SKILL.md new file mode 100644 index 00000000000..c31b6a666fc --- /dev/null +++ b/skills/performance-review/SKILL.md @@ -0,0 +1,33 @@ +--- +name: performance-review +description: Use for TanStack Router/Start repository performance investigations and changes to resource lifetimes, hydration/RSC, HMR, compiler/editor cost, dependencies, or chunk caching. Covers measurement and regression checks; use bundle-size-optimization for emitted-byte optimization. +--- + +# Performance review + +Investigate a specific Router/Start mechanism with a comparable baseline and observable behavior. Follow the repository's build/test constraints in [AGENTS.md](../../AGENTS.md). Documentation-only changes that do not affect these mechanisms need their documentation checks, not this investigation. + +## Select checks + +Read [comparable measurements](references/checks.md#comparable-measurements) for every measurement, then the applicable sections below. Combine sections when a change crosses boundaries. + +| Mechanism | Reference | +| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Runtime work, cleanup, hydration, RSC, or devtools | [Runtime and lifecycle checks](references/checks.md#runtime-and-lifecycle-checks); for live requests/rendering, also [browser and server measurements](references/checks.md#browser-and-server-measurements) | +| Compiler work, maps, diagnostics, metadata, or module identity | [Build-tool checks](references/checks.md#build-tool-checks) | +| Hot updates, route deletion, or watcher replacement | [HMR and watch lifetimes](references/checks.md#hmr-and-watch-lifetimes) and the affected runtime/build checks | +| Dependency versions or bundler compatibility | [Dependency upgrades](references/checks.md#dependency-upgrades) and the affected adapter checks | +| Vendor/runtime chunks or deployment recovery | [Chunk caching and deployment recovery](references/checks.md#chunk-caching-and-deployment-recovery) | +| TypeScript inference or compiler cost | [TypeScript cost](references/checks.md#typescript-cost) | + +## Investigate and verify + +1. Choose a representative workload and metric for the affected code. Reuse repository fixtures and installed tools. Attribute cost to code, configuration, a dependency, the toolchain, or the harness before choosing a fix; avoid speculative caches, indexes, concurrency, or dependency changes. +2. Exercise the affected packages and adapters through their real consumers. Source inspection or one adapter's passing tests cannot establish behavior in another adapter. +3. Establish a supported trigger and baseline before editing. Diagnostic counters must not create the work they claim to observe. Change one variable per control, check equivalent behavior, then run the same protocol on the final implementation. Use profiles or operation counts to explain timing, not a source-level suspicion as proof of a slowdown. +4. Verify each material finding against its owning source and actual check. Promote reproduced failures into existing behavioral tests or stable operation/artifact bounds; avoid noisy elapsed-time assertions. Assert the effect that exposed the bug: execution count, requests, retained resources, or intermediate failure, as well as final output. Preserve request isolation, side-effect ordering, required waits, and intentional cache/replay lifetimes. +5. Rerun the regression and relevant performance checks on the final implementation. Experimental controls can isolate a cause but do not replace tests of the actual fix. + +When changes interact, test both their individual and combined behavior where applicable. Recheck the combined implementation after repairs; a clean textual merge does not establish behavioral compatibility. + +For emitted client JavaScript, continue through the [bundle-size-optimization skill](../bundle-size-optimization/SKILL.md). Use the same implementation for behavior checks and bundle measurements. diff --git a/skills/performance-review/references/checks.md b/skills/performance-review/references/checks.md new file mode 100644 index 00000000000..cff3a7bbc16 --- /dev/null +++ b/skills/performance-review/references/checks.md @@ -0,0 +1,91 @@ +# Performance checks + +Reference for the [performance-review skill](../SKILL.md). Read the sections relevant to the changed mechanism and affected consumers; the skill defines the investigation procedure. + +Select the existing workload for [client navigation](../../../benchmarks/client-nav/README.md), [Start SSR](../../../benchmarks/ssr/README.md), or [memory](../../../benchmarks/memory/README.md). Compare identical baseline/candidate workloads and add coverage when existing scenarios miss the changed mechanism. There is no aggregate build-CPU benchmark in that set: use the build-tool checks below. For hydration, RPC, paint, or real HTTP behavior, use the affected browser/app workload and the browser/server measurement procedure. For inference cost, use the TypeScript procedure. + +## Comparable measurements + +Compare the intended baseline and candidate implementations with the same benchmark source, workload, runtime/tool versions, framework, adapter, build mode, cache state, and runner settings. Keep scenario selections and measurement flags identical. Use separate results/dist directories and avoid unrelated active builds during timing runs. + +Check the benchmark's actual base, environment warnings, skipped cases, baseline substitutions, and cached results before comparing implementations. A successful job may reuse earlier measurements or skip the affected scenario. Plain memory smoke runs do not supply CodSpeed memory metrics. + +Measure operation counts, retained bytes, CPU, elapsed time, throughput, initial payload, and later loading cost separately. Fewer calls do not necessarily reduce elapsed time, raw-byte reductions may increase gzip size, and a larger RSS sample alone does not establish a leak. Use sample count, variance/error, and relevant percentiles to distinguish a change from measurement noise; narrow or rerun noisy cases. + +## Build-tool checks + +Use the actual adapter and installed compiler version. For transform/build performance, source maps, production diagnostics, shared module state, or Webpack/Rspack behavior, exercise the affected adapter. The official bundle runner covers Vite and explicit Rsbuild scenarios; changes affecting Webpack or shared splitting/DCE also need a real `@tanstack/router-plugin/webpack` consumer. Start's Vite/Rsbuild adapters, direct Rspack, and Router's Webpack adapter are distinct validation surfaces. Snapshot output and Vite results do not establish actual Webpack loader, mode, cache, or chunk behavior. Inspect existing E2E project scripts/configuration to select executable targets rather than assuming an aggregate benchmark exists. + +For the changed mechanism, cover these boundaries: + +| Boundary | Check | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Build work | Profile resolve/load, parse, analysis, reference/virtual/shared generation, maps, and emit separately. Include ignored files and no-transform routes. Check per-transform reuse and AST mutation ownership before introducing a persistent cache. Compare cold builds, warm builds, and no-op rebuilds. | +| Production diagnostics | Distinguish build-host environment checks from conditions emitted into client code. Exercise production compiler mode with host `NODE_ENV` unset and production, plus development behavior. Check emitted diagnostics, absolute paths, and execution; production mode cannot erase an unconditional warning already generated by a plugin. | +| Source maps | Trace the actual loader chain and map consumer. Cover `devtool: false`, a source-map-enabled build, and incoming-map present/absent. Preserve source locations, `sourcesContent`, and downstream diagnostics; measure discarded map construction separately from JS output and wall time. | +| Metadata and assets | Trace indexes, source text, CSS, and stats to their readers and enabling options. Verify disabled/enabled consumers and output equivalence before removing work. Avoid making a second index or capture that no consumer reads. | +| Identity and invalidation | Exercise independent compiler instances, concurrent builds, aliases/query variants, route edits/add/remove, shared dependency and configuration changes, and error recovery. Preserve watch dependencies, module identity, HMR state, and cleanup; unchanged inputs should avoid unnecessary work without hiding changed inputs. | +| Shipped graph | Inspect resolved exports/entry conditions, ESM/CJS paths, module reasons, shared/async chunks, and concatenation bailouts. Verify unused code disappears while required CSS/global effects remain and shared modules initialize once. Measure initial and async payloads and execute navigation; do not infer all bundlers honor a Rolldown annotation. | + +Use the same fixture/configuration for baseline and candidate; vary one option deliberately when isolating a cause. Validate annotation and loader behavior from the installed dependency source or matching official documentation. A snapshot or one-shot build cannot establish watch/HMR correctness. Restore fixture edits and close owned watchers/compilers after failures. An environment failure such as `EMFILE` is a coverage blocker, not a library performance finding. + +Reference: Webpack's [build-performance](https://webpack.js.org/guides/build-performance/), [mode](https://webpack.js.org/configuration/mode/), and [tree-shaking](https://webpack.js.org/guides/tree-shaking/) documentation. + +For Start metadata changes, trace normalized indexes, asset text, CSS content, and other metadata from producer to actual consumers before retaining or removing them. Test the consuming feature both disabled and enabled, including inline CSS, and preserve full manifest/output behavior in Vite and Rsbuild. + +### HMR and watch lifetimes + +- Verify the fixture's framework refresh integration before attributing a failure to Router or the compiler. Exercise repeated component, route-option, server-function, and CSS edits where affected; check visible changes and intentional state preservation. A window sentinel or document-request count must distinguish hot updates from a full reload; allow reloads only for transitions whose contract requires them. +- For route deletion or configuration replacement, capture generated source, errors, and emitted assets from the same compilation. Check the first affected compilation as well as eventual recovery. Replace configuration while generation is pending, then dispatch later file events; only the current generator should write, and metadata for deleted routes must be removed. One final successful build can hide an earlier stale build or duplicate work. +- Restore source edits through the existing HMR helper after failures. Await the expected source/build/browser state instead of adding fixed sleeps or manual reloads that hide broken updates. + +### Dependency upgrades + +Compare the same consumer on the existing and candidate dependency stacks. Record actual resolved versions, required companion runtimes, and supported peer contracts from matching upstream sources; a passing fixture does not override a documented compatibility requirement. Keep upgrade effects separate from application configuration changes. + +For a failure on both stacks, investigate the existing integration or fixture before calling it an upgrade regression. For a candidate-only failure, reduce a matched old/new reproduction, check upstream issues, and identify the owning layer before adding a workaround. Do not relax checks or dependency policy to make an upgrade pass. + +### Chunk caching and deployment recovery + +Measure cold-load bytes, request count, and loading time separately from downloads after unchanged and edited deployments. Use real HTTP cache headers and control cache state. Inspect resolved module paths before assigning vendor groups; workspace-linked packages may not match a `node_modules` rule. Treat chunk layout as application/framework policy unless the plugin contract explicitly owns it. + +Keep an old page open across deployment, then navigate to a previously unloaded route. Cover retained and removed old hashed assets. For chunk recovery, verify preload does not reload the page, navigation can recover through the bounded reload policy, a persistently missing chunk reaches the error UI without a loop, and unrelated errors do not trigger recovery. Reloading the page between deployments does not exercise this boundary. + +## Runtime and lifecycle checks + +- For optional features, measure disabled/idle work as well as enabled work. Gate unnecessary allocation, subscriptions, scans, and decoding before setup where safe; a handler that immediately returns still has registration cost. Preserve user handlers and off/on/off transitions. +- For listeners, subscriptions, observers, timers, caches, and patched globals, name the creator, update owner, lifetime, and cleanup path. Start pending work or populate retained state before testing cancellation, failure, replacement, and disposal. Test overlapping instances, both disposal orders, and events after disposal. Verify which owner receives subsequent work, not just whether cleanup ran. Apply this to scroll restoration across router lifetimes as well as framework unmounts; a component unmount does not necessarily end a shared history's lifetime. +- For native history wrappers or teardown, exercise two histories sharing one window, destroy them in both orders, and dispatch later native updates. A surviving history must keep receiving updates; destroying the second must not restore a wrapper owned by the already-destroyed first history. +- For hydration changes, cover initial SSR hydration, CSR, later-mounted boundaries, unrelated ancestor updates, intentional option changes, and disposal. Define which event starts or resets a deadline; test inline/equivalent strategy values and browsers with and without the relevant scheduling API. Queue a real interaction while loading is pending before cancelling or removing the boundary; check obsolete replay records are released while surviving nested boundaries and successful replay still work. Preserve intentional pending-UI waits and loader/context ordering. +- For head, Scripts, Asset, or injected-code changes, assert execution counts through attribute/content combinations, hydration, navigation, and removal. Final markup can hide duplicate side effects; distinguish elements, fetch attempts, transferred bytes, and executions. +- For repeated scans or recursive transforms, exercise increasing input sizes and supported empty, deep, wide, shared-reference, and cyclic inputs. Count work separately from elapsed time; preserve ordering, identity, and serialization behavior before retaining a faster implementation. + +Before adding a cache or index, check existing subtree APIs, adjacency data, and reuse within the current transform or request. Measure work at its real frequency: per root, file, project, route, or render. Preserve AST mutation ownership and complete diagnostic messages, locations, and ordering. A persistent cache also needs an owner, invalidation on edits/deletion/configuration changes, and a measured retention bound; fewer visits alone does not justify longer-lived state. + +### Router devtools + +- Measure closed-panel setup and idle callbacks with large route/cache state as well as open-panel interactions. Development-only code still has a user-visible cost; distinguish default development exports from explicit production opt-ins. +- Count style-factory and template-processing work separately from inserted CSS. Before reusing styles, preserve document/shadow-root ownership, theme updates, HMR, and target disposal; stylesheet deduplication alone does not prove the construction work is absent. +- For React/Solid/Vue wrappers, replace router A with B while mounted, navigate each, then unmount and update again. Only the current router should drive the panel, and unmounted wrappers should receive no further work. Test floating and standalone wrappers when both are affected. + +### React Start RSC + +- For diagnostic slot previews and recursive payload transforms, validate supported shared-reference, deep, and cyclic inputs before timing them. Traversal state must actually bound repeated work; any reuse must preserve the consumer's serialization contract. +- Gate development diagnostics before constructing payloads, then measure their development cost. Treat replay needed by SSR and later serialization as a required lifetime until evidence shows retention beyond it; validate cancellation and request cleanup before calling it a leak. +- For asset decoding/preloading, return both rendered and unused RSC values through the supported server-function path. Check client navigation and cold/warm SSR, using fresh browser contexts against a reused server. Verify unused values do not load client JS or apply CSS, and later rendering still loads dependencies. Cover streams with and without serialized asset metadata and both renderable and composite components. +- For stylesheet changes, identify whether the framework renderer or bundler creates, updates, and removes each link. Prefer the existing owner's lifecycle over competing resource registration. Check repeated edits and property removal using computed styles and bounded link counts; visible correctness alone can conceal stale stylesheets. A Vite-specific ownership fix must preserve Rsbuild behavior. +- For deferred rendering or refresh, delay stylesheet delivery and verify content is styled when revealed, existing content remains visible while replacement is pending, and hydration reports no mismatch. Check production Vite and Rsbuild, including JavaScript-disabled SSR. Keep browser-only resource handling from making streamed SSR content depend on JavaScript. + +## Browser and server measurements + +Verify the fixture before measuring: production versus development server, framework bootstrap/hydration support, native scheduling and browser capabilities, actual response consumption, and assertions that the intended path ran. A server-CPU fixture may not be a valid browser-hydration app. Release harness-owned routers, DOM references, console records, and request bodies before drawing retention conclusions; inspect reachability/retainers or repeated bounded workloads. + +For changed hydration/RPC/streaming behavior, exercise real HTTP/browser consumption, later navigation, cancellation, errors, and disposal as applicable. Keep CPU, Router `onRendered`, hydration completion, presentation/INP, fetch attempts, transferred bytes, and throughput distinct. Name tested engines; Playwright WebKit is not branded Safari. Reuse working app bootstrap and error-checking fixtures, correct harness defects, and rerun before attributing a failure to the product. + +## TypeScript cost + +Use the repository typecheck/project-reference setup and a representative consumer route tree at increasing sizes. Keep TypeScript version, compiler configuration, fixture, and cold/warm state identical between implementations. Preserve inference assertions and measure check time, compiler work, and memory where the installed compiler exposes them. Do not pass individual source files to `tsc` and thereby bypass project configuration. A passing typecheck does not establish unchanged compiler/editor performance. + +## Reviewing changes to this guidance + +When updating this guide, check that its triggers lead to the relevant procedures and that linked commands, fixtures, and references remain valid. Walk through an applicable change, an unrelated change, and a failed or unavailable check to identify missing instructions. Formatting and link checks alone do not establish that the procedures work.