feat(test): type renderRoute ids, inputs, and results from the generated route registration - #456
Conversation
…ted route registration
The compiler's .agent-bundle/routes.d.ts now registers an
AgentBundleRouteContracts map ({ input, result } per route id) on
@agent-bundle/runtime's new Register interface, in the same
declare-module block that already declares provider keys. agent-bundle/test's
renderRoute and renderRouteEvents infer the route id, input, and result from
that registration (TanStack Router's Register / ConstrainLiteral pattern);
without the generated file in the program they keep the previous types
(any string id, unknown input and result).
Adds RegisteredRoutes / RegisteredRouteContract / RegisteredRouteId /
RegisteredRouteInput / RegisteredRouteResult to @agent-bundle/runtime and
RouteTargetConstraint / RouteTargetInput / RouteTargetResult to
agent-bundle/test; a route-register-typegen integration test proves the
registered, mistyped, and unregistered programs; en/zh testing docs,
framework-mode.md, and the package README describe the seam.
Started by the TanStack cross-reference lane; landed from its parked work.
🦋 Changeset detectedLatest commit: 3c8b7da The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab4051e31f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| 'export type RouteResult<Id extends RouteId> = ContractResult<AgentBundleRoutes[Id]>;', | ||
| '/** The registered contract map: one `{ input, result }` per route id, for `@agent-bundle/runtime`\'s `Register`. */', | ||
| 'export type AgentBundleRouteContracts = {', | ||
| ' readonly [Id in RouteId]: Readonly<{ input: RouteInput<Id>; result: RouteResult<Id> }>;', |
There was a problem hiding this comment.
Register the event harness payload instead of component props
For a typed event component using AgentEventRouteProps, this maps the route input to { canonical, native, signal } and the result to the component's React return value. However, renderRoute expects options.input to contain only { canonical, native }, injects its own signal in componentProps, and exposes no result because event modules have no resultSchema. Once the generated declaration is included, valid event-route calls therefore fail for a missing signal, while the result type describes a value the harness never returns; event registrations need a harness-specific payload/result contract rather than the component signature.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 3c8b7da. The registered map (AgentBundleRouteContracts) is now built from two harness-specific helpers rather than the component-props RouteInput/RouteResult: HarnessInput yields the schema output for schema routes and Omit<Props, 'signal'> — i.e. { canonical, native } — for event routes, since the harness injects signal itself; HarnessResult yields the resultSchema output for schema routes and undefined for event routes, which export none. RouteInput/RouteResult keep their existing meaning (what the component receives). Tests: route-graph.test.ts pins the generated helpers and asserts Register['routes']['event:workspace/open'] equals { input: Omit<Props, 'signal'>; result: undefined } in a real tsc program; route-register-typegen.test.ts now compiles an event:tool/after route typed with AgentEventRouteProps against the built packages — a { canonical, native } call is accepted, result is undefined, and a mistyped canonical is rejected with AgentEventCanonicalIdentity named. Docs (en/zh testing guide, framework-mode, README) and the Register doc comment state the event-route contract.
| "agent-bundle": patch | ||
| --- | ||
|
|
||
| Type `renderRoute` and `renderRouteEvents` (from `agent-bundle/test`) against the project's own routes: the generated `.agent-bundle/routes.d.ts` now registers an `AgentBundleRouteContracts` map (`{ input, result }` per route id) on `@agent-bundle/runtime`'s new `Register` interface, so once that file is in the project's TypeScript program a string-literal route id is checked against the compiled ids, `input` is typed from the route's `inputSchema`, and `result` from its `resultSchema`. `@agent-bundle/runtime` exports `Register`, `RegisteredRoutes`, `RegisteredRouteContract`, `RegisteredRouteId`, `RegisteredRouteInput`, and `RegisteredRouteResult`; `agent-bundle/test` exports `RouteTargetConstraint`, `RouteTargetInput`, and `RouteTargetResult`. Nothing is required: a value typed `string`, a module target, or a program without the generated file sees the previous types (any id, `unknown` input and result). |
There was a problem hiding this comment.
Rewrite the changeset summary in the required format
This changeset is a long implementation description and ends without the required (#PR) suffix. Rewrite it as a concise, user-facing imperative summary naming the affected exports and ending with the PR number, as required by the repository changeset convention.
AGENTS.md reference: AGENTS.md:L104-L105
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Rewritten in 3c8b7da: imperative, names the affected entry points and every new export (Register, RegisteredRoutes, RegisteredRouteContract, RegisteredRouteId, RegisteredRouteInput, RegisteredRouteResult; RouteTargetConstraint, RouteTargetInput, RouteTargetResult), states the degraded behaviour without the generated file, and ends with (#456).
…nical, native } input, no result; tighten the changeset
Summary
Lands the implementation candidate from the TanStack Router cross-reference lane: typed route ids, inputs, and results for the
agent-bundle/testharness, through the generated.agent-bundle/routes.d.tsand aRegisterseam on@agent-bundle/runtime— TanStack Router's registration pattern (typed routes without per-route codegen: onedeclare moduleaugmentation, inference everywhere else).packages/agent-bundle/src/routes/typegen.ts: the generated declarations now emitAgentBundleRouteContracts({ input, result }per route id) and register it asRegister.routesin the samedeclare module '@agent-bundle/runtime'block that already declares provider keys (one block; omitted when the graph has neither routes nor providers).packages/rsc-runtime/src/agent-request.ts: new emptyRegisterinterface plusRegisteredRoutes,RegisteredRouteContract,RegisteredRouteId,RegisteredRouteInput,RegisteredRouteResult, all degrading tostring/unknownwhen nothing is registered. Exported from@agent-bundle/runtime.inputis the{ canonical, native }payload (the harness injectssignal),resultisundefined(event modules export noresultSchema).RouteInput/RouteResultkeep their component-props meaning.packages/agent-bundle/src/test/render.ts:renderRoute/renderRouteEventsinferTargetfrom a string literal and check it with theConstrainLiteralshape(Target & Constraint) | Constraint, so a typo is rejected naming the registered ids;inputandresultare typed from the registered contract. A value typedstring, a module target, or an unregistered program keep the previous types. New exported typesRouteTargetConstraint,RouteTargetInput,RouteTargetResult.website/docs/{en,zh}/guide/development/testing.mdx,docs/framework-mode.md,packages/agent-bundle/README.md..changeset/typed-route-register.md(@agent-bundle/runtimepatch,agent-bundlepatch — additive).Not included: the lane's architecture matrix document (react-router-rsbuild / Next.js / TanStack vs agent-bundle) was never written before the worker halted; nothing of it existed to land.
Evidence
pnpm typecheckexit 0;pnpm lintexit 0 (0 errors, 0 warnings).route-graph.test.ts(unit): 50 passed — typegen output pinned, including the single augmentation block and theRegister['routes']↔RouteIdequality in a realtscprogram.pnpm buildexit 0 (publint passed); thenroute-register-typegen.test.ts+public-api.test.ts+provider-typegen.test.tsunder the integration config withAGENT_BUNDLE_*_PREBUILT=1: 9 passed, 0 failed. The fixture now also compiles anevent:tool/afterroute (AgentEventRouteProps,claudetarget):{ canonical, native }accepted,resulttypedundefined, mistypedcanonicalrejected namingAgentEventCanonicalIdentity.3c8b7da99:pnpm test:unit3026 passed;pnpm test:route-unit44 passed;pnpm test:projection145 passed;pnpm typecheckexit 0;pnpm lint0 errors/0 warnings;pnpm docs:site:buildexit 0. The new test type-checks a fixture against the builtagent-bundle/testand@agent-bundle/runtime: registered program clean; wrong id rejected with'"tool:curator/find" | "tool:curator/status"'; wrong input and wrong result rejected; unregistered program (no generated file) accepts any id andunknown.pnpm docs:site:buildexit 0 (TypeDoc over the new exports, dead-link and language-parity checks pass).Test plan
3c8b7da99(the earlierRelease gatesfailure on18a384a71was thenpm audithang inaudit-packed-release.mjs, also failing onmainat284141958; not this change)Review status
Codex reviewed
ab4051e31and raised two P1 threads (event-route contract; changeset format); both fixed in3c8b7da99and answered on-thread. No re-review requested (maintainer process change 2026-09-03). Self-review: the generated augmentation references@agent-bundle/runtimefor any graph with routes (previously only provider graphs did); a project that adds.agent-bundle/routes.d.tsto itstsconfigincludewithout depending on@agent-bundle/runtimewould see "invalid module name in augmentation" — the scaffold pins both packages and no example includes the file, so nothing in-tree is affected; documented as opt-in in the testing guide.